package top.zhangjianyong.blog.aspect;

import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import top.zhangjianyong.blog.annotation.ApiLog;
import top.zhangjianyong.blog.response.ApiResponse;

/**
 * API接口日志切面 用于记录接口调用的详细日志，方便问题分析和性能监控
 *
 * @author zhangjianyong
 * @since 2024/12/27
 */
@SuppressWarnings("LoggingSimilarMessage")
@Slf4j
@Aspect
@Component
@Order(10) // 设置较低优先级，确保在权限校验等切面之后执行
public class ApiLogAspect {

  @Pointcut(
      "execution(public * top.zhangjianyong.blog.portal.controller.*.*(..))"
          + "|| execution(public * top.zhangjianyong.blog.manage.controller.*.*(..))")
  public void apiPointcut() {}

  @Around("apiPointcut()")
  public Object logApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
    // 检查是否需要记录日志
    ApiLog apiLogAnnotation = getApiLogAnnotation(joinPoint);
    if (apiLogAnnotation != null && apiLogAnnotation.ignore()) {
      return joinPoint.proceed();
    }

    long startTime = System.currentTimeMillis();
    String methodName = getMethodName(joinPoint);

    try {
      // 记录请求开始日志
      logRequestStart(joinPoint, methodName, apiLogAnnotation);

      // 执行目标方法
      Object result = joinPoint.proceed();

      // 记录成功响应日志
      logRequestSuccess(methodName, result, startTime, apiLogAnnotation);

      return result;

    } catch (Exception e) {
      // 记录异常日志
      logRequestError(methodName, e, startTime, apiLogAnnotation);
      throw e;
    }
  }

  /** 记录请求开始日志 */
  private void logRequestStart(
      ProceedingJoinPoint joinPoint, String methodName, ApiLog apiLogAnnotation) {
    try {
      HttpServletRequest request = getCurrentRequest();
      if (request != null) {
        log.info("┌─────────────────────────────────────────────────────────────");
        log.info("│ 🚀 API请求开始");
        log.info("│ 接口方法: {}", methodName);

        // 显示接口描述（如果有）
        if (apiLogAnnotation != null && !apiLogAnnotation.description().isEmpty()) {
          log.info("│ 接口描述: {}", apiLogAnnotation.description());
        }

        log.info("│ 请求URL: {} {}", request.getMethod(), request.getRequestURL());
        log.info("│ 客户端IP: {}", getClientIP(request));
        log.info("│ User-Agent: {}", request.getHeader("User-Agent"));

        // 记录请求头（可选，根据需要开启）
        if (log.isDebugEnabled()) {
          logRequestHeaders(request);
        }

        // 记录请求参数（根据注解配置）
        if (apiLogAnnotation == null || apiLogAnnotation.logRequest()) {
          Object[] args = joinPoint.getArgs();
          if (args != null && args.length > 0) {
            Object[] filteredArgs =
                Arrays.stream(args)
                    .filter(arg -> !(arg instanceof MultipartFile))
                    .filter(arg -> !(arg instanceof HttpServletResponse))
                    .filter(arg -> !(arg instanceof HttpServletRequest))
                    .toArray();

            if (filteredArgs.length > 0) {
              log.info("│ 请求参数: {}", JSONObject.toJSONString(filteredArgs));
            }
          }
        }
        log.info("└─────────────────────────────────────────────────────────────");
      }
    } catch (Exception e) {
      log.warn("记录请求开始日志失败: {}", e.getMessage());
    }
  }

  /** 记录成功响应日志 */
  private void logRequestSuccess(
      String methodName, Object result, long startTime, ApiLog apiLogAnnotation) {
    long duration = System.currentTimeMillis() - startTime;
    log.info("┌─────────────────────────────────────────────────────────────");
    log.info("│ ✅ API请求成功");
    log.info("│ 接口方法: {}", methodName);

    // 记录执行时间（根据注解配置）
    if (apiLogAnnotation == null || apiLogAnnotation.logTime()) {
      log.info("│ 执行耗时: {}ms", duration);
    }

    // 记录响应结果（根据注解配置）
    if (result != null && (apiLogAnnotation == null || apiLogAnnotation.logResponse())) {
      if (result instanceof ApiResponse apiResponse) {
        log.info("│ 响应状态: {} - {}", apiResponse.getCode(), apiResponse.getMessage());

        // 只在debug模式下记录完整响应数据
      } else {
        log.info("│ 响应类型: {}", result.getClass().getSimpleName());
      }
      if (log.isDebugEnabled()) {
        log.debug("│ 响应数据: {}", JSONObject.toJSONString(result));
      }
    }

    // 性能警告
    if (duration > 1000) {
      log.warn("│ ⚠️  接口响应时间过长: {}ms", duration);
    } else if (duration > 500) {
      log.info("│ ⏰ 接口响应时间较长: {}ms", duration);
    }

    log.info("└─────────────────────────────────────────────────────────────");
  }

  /** 记录异常日志 */
  private void logRequestError(
      String methodName, Exception e, long startTime, ApiLog apiLogAnnotation) {
    long duration = System.currentTimeMillis() - startTime;

    log.error("│ ❌ API请求异常");
    log.error("│ 接口方法: {}", methodName);

    // 记录执行时间（根据注解配置）
    if (apiLogAnnotation == null || apiLogAnnotation.logTime()) {
      log.error("│ 执行耗时: {}ms", duration);
    }
    log.error("│ 异常类型: {}", e.getClass().getSimpleName());
    log.error("│ 异常信息: {}", e.getMessage());

    // 记录完整堆栈信息
    if (log.isDebugEnabled()) {
      log.debug("│ 异常堆栈: ", e);
    }

    log.error("└─────────────────────────────────────────────────────────────");
  }

  /** 记录请求头信息（调试模式） */
  private void logRequestHeaders(HttpServletRequest request) {
    Map<String, String> headers = new HashMap<>();
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()) {
      String headerName = headerNames.nextElement();
      // 过滤敏感信息
      if (!headerName.toLowerCase(Locale.ROOT).contains("authorization")
          && !headerName.toLowerCase(Locale.ROOT).contains("cookie")) {
        headers.put(headerName, request.getHeader(headerName));
      }
    }
    if (!headers.isEmpty()) {
      log.debug("│ 请求头: {}", JSONObject.toJSONString(headers));
    }
  }

  /** 获取当前请求对象 */
  private HttpServletRequest getCurrentRequest() {
    try {
      ServletRequestAttributes attributes =
          (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
      return attributes.getRequest();
    } catch (Exception e) {
      return null;
    }
  }

  /** 获取客户端真实IP */
  private String getClientIP(HttpServletRequest request) {
    String xForwardedFor = request.getHeader("X-Forwarded-For");
    if (xForwardedFor != null
        && !xForwardedFor.isEmpty()
        && !"unknown".equalsIgnoreCase(xForwardedFor)) {
      return xForwardedFor.split(",")[0].trim();
    }

    String xRealIP = request.getHeader("X-Real-IP");
    if (xRealIP != null && !xRealIP.isEmpty() && !"unknown".equalsIgnoreCase(xRealIP)) {
      return xRealIP;
    }

    return request.getRemoteAddr();
  }

  /** 获取ApiLog注解 */
  private ApiLog getApiLogAnnotation(ProceedingJoinPoint joinPoint) {
    try {
      // 先从方法上查找注解
      String methodName = joinPoint.getSignature().getName();
      Class<?> targetClass = joinPoint.getTarget().getClass();
      Method[] methods = targetClass.getDeclaredMethods();

      for (Method method : methods) {
        if (method.getName().equals(methodName)) {
          ApiLog methodAnnotation = method.getAnnotation(ApiLog.class);
          if (methodAnnotation != null) {
            return methodAnnotation;
          }
          break;
        }
      }

      // 如果方法上没有注解，则查找类上的注解
      return targetClass.getAnnotation(ApiLog.class);

    } catch (Exception e) {
      log.warn("获取ApiLog注解失败: {}", e.getMessage());
      return null;
    }
  }

  /** 获取方法全名 */
  private String getMethodName(ProceedingJoinPoint joinPoint) {
    String className = joinPoint.getTarget().getClass().getSimpleName();
    String methodName = joinPoint.getSignature().getName();
    return className + "." + methodName + "()";
  }
}
